package com.ideaaedi.zoo.example.springboot.seata.alpha.service;

import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ideaaedi.zoo.diy.artifact.seata.transaction.SeataManualTransactionSupport;
import com.ideaaedi.zoo.example.springboot.seata.alpha.entity.SysUserPO;
import com.ideaaedi.zoo.example.springboot.seata.alpha.feign.BetaFeignClient1;
import com.ideaaedi.zoo.example.springboot.seata.alpha.feign.BetaFeignClient2;
import com.ideaaedi.zoo.example.springboot.seata.alpha.feign.BetaFeignClient3;
import com.ideaaedi.zoo.example.springboot.seata.alpha.feign.RawHttpRequest4;
import com.ideaaedi.zoo.example.springboot.seata.alpha.mapper.SysUserMapper;
import io.seata.spring.annotation.GlobalTransactional;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;


@Slf4j
@Service
public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUserPO> implements SysUserService {
    
    @Resource
    private SysUserService sysUserService;
    
    @Resource
    private BetaFeignClient1 betaFeignClient1;
    
    @Resource
    private BetaFeignClient2 betaFeignClient2;
    
    @Resource
    private BetaFeignClient3 betaFeignClient3;
    
    @Resource
    private RawHttpRequest4 betaFeignClient4;
    
    /**
     * 未开启分布式事务前：调用此方法后， sys_user不会新增任何数据；而sys_role将成功新增一条数据
     * 开启分布式事务后：调用此方法后， sys_user和sys_role都不会新增仍和数据
     * <p>
     * 注：与本地事务@Transactional的识别机制一样，只有当@GlobalTransactional感知到异常时，才会自动全局回滚。
     *    所以，如果失败了，需要抛出异常，而不要try catch掉异常
     * 注：一般的，使用了@GlobalTransactional后就不需要使用@Transactional了。但是当一个分支事务（本地事务）中存在多个dml时，
     *     建议使用@GlobalTransactional的同时，也使用@Transactional
     * 注：与本地事务@Transactional类似，@GlobalTransactional也可以嵌套@GlobalTransactional进行使用
     * 注：当与本地事务结合@Transactional和@GlobalTransactional连用时，@Transactional
     *     只能位于标注在@GlobalTransactional的同一方法层次或者位于@GlobalTransaction 标注方法的内层。这里分布式事务的概念要大于本地事务，若将 @Transactional
     *     标注在外层会导致分布式事务空提交，当@Transactional 对应的 connection 提交时会报全局事务正在提交或者全局事务的xid不存在。
     *
     * <p><p><p>
     * <pre>
     *  注意事项（更多详见 https://seata.apache.org/zh-cn/docs/overview/faq/ ）：
     *
     * 3. 业务表中必须包含单列主键，若存在复合主键，请参考问题 13 。
     *
     * 4. 每个业务库中必须包含 undo_log 表，若与分库分表组件联用，分库不分表。
     *
     * 5. 跨微服务链路的事务需要对相应 RPC 框架支持，目前 seata-all 中已经支持：Apache Dubbo、Alibaba Dubbo、sofa-RPC、Motan、gRpc、httpClient，对于
     * Spring Cloud 的支持，请大家引用 spring-cloud-alibaba-seata。其他自研框架、异步模型、消息消费事务模型请结合 API 自行支持。
     *
     * 6. 目前AT模式支持的数据库有：MySQL、Oracle、PostgreSQL和 TiDB。
     *
     * 7. 使用注解开启分布式事务时，若默认服务 provider 端加入 consumer 端的事务，provider 可不标注注解。但是，provider 同样需要相应的依赖和配置，仅可省略注解。
     *
     * 8. 使用注解开启分布式事务时，若要求事务回滚，必须将异常抛出到事务的发起方，被事务发起方的 @GlobalTransactional 注解感知到。provide 直接抛出异常 或 定义错误码由 consumer
     * 判断再抛出异常。
     *
     * 9. AT 模式和 Spring @Transactional 注解连用时需要注意什么 ？
     *
     *    注解@Transactional 可与 DataSourceTransactionManager 和 JTATransactionManager
     *    连用分别表示本地事务和XA分布式事务，大家常用的是与本地事务结合。当与本地事务结合时，@Transactional和@GlobalTransactional连用，@Transactional
     *    只能位于标注在@GlobalTransactional的同一方法层次或者位于@GlobalTransaction 标注方法的内层。这里分布式事务的概念要大于本地事务，若将 @Transactional
     *    标注在外层会导致分布式事务空提交，当@Transactional 对应的 connection 提交时会报全局事务正在提交或者全局事务的xid不存在。
     * </pre>
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    @GlobalTransactional(rollbackFor = Exception.class)
    public SysUserPO test() {
        // 1. 本地添加用户A成功
        SysUserPO sysUser = new SysUserPO();
        sysUser.setId(IdWorker.getId());
        sysUser.setName("用户A");
        save(sysUser);
        
        // 2. feign调用beta微服务添加角色成功
        // 示例几种常用的 restful调用远程服务的方式, 这几种方式seata都是支持的； 当然seata还支持更多的restful调用远程服务的方式，详见seata官网
        //betaFeignClient1.randomAddRole();
        //betaFeignClient2.randomAddRole();
        //OnceFeignProvider.voidExec(() -> betaFeignClient3.randomAddRole(), "http://127.0.0.1:8081");
        betaFeignClient4.randomAddRole();
        
        
        // 3. 本地添加随机用户B失败
        sysUser = new SysUserPO();
        sysUser.setId(IdWorker.getId());
        sysUser.setName("用户B");
        save(sysUser);
        System.err.println(1 / 0);// todo 在这里打断点，进入断点后观察两张表的数据；然后放行断点跑完请求，再观察两张表的数据； 对比可知：分布式事务生效了
        return null;
    }
    
    
    /**
     * 测试GlobalTransactional注解嵌套
     * <p>
     *     结论：可嵌套使用
     * </p>
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    @GlobalTransactional(rollbackFor = Exception.class)
    public SysUserPO testNested() {
        SysUserPO sysUser = new SysUserPO();
        sysUser.setId(IdWorker.getId());
        sysUser.setName("用户A");
        save(sysUser);
    
        // 示例几种常用的 restful调用远程服务的方式, 这几种方式seata都是支持的； 当然seata还支持更多的restful调用远程服务的方式，详见seata官网
        //betaFeignClient1.randomAddRole();
        //betaFeignClient2.randomAddRole();
        //OnceFeignProvider.voidExec(() -> betaFeignClient3.randomAddRole(), "http://127.0.0.1:8081");
        betaFeignClient4.randomAddRole();
        
        /*
         * Transactional可以嵌套使用，那么GlobalTransactional是否可以签到使用呢？
         * 这里进行测试
         */
        sysUserService.addRandomUserRole();
        
        sysUser = new SysUserPO();
        sysUser.setId(IdWorker.getId());
        sysUser.setName("用户B");
        save(sysUser);
        System.err.println(1 / 0);// todo 在这里打断点，进入断点后观察两张表的数据；然后放行断点跑完请求，再观察两张表的数据； 对比可知：分布式事务生效了
        return null;
    }
    
    /**
     * 手动事务测试。效果等价于 {@link SysUserService#test()} ()}
     */
    @Override
    public SysUserPO testManual() {
        return SeataManualTransactionSupport.create().exec(() -> {
            SysUserPO sysUser = new SysUserPO();
            sysUser.setId(IdWorker.getId());
            sysUser.setName("用户A");
            save(sysUser);
            
            // 示例几种常用的 restful调用远程服务的方式, 这几种方式seata都是支持的； 当然seata还支持更多的restful调用远程服务的方式，详见seata官网
            //betaFeignClient1.randomAddRole();
            //betaFeignClient2.randomAddRole();
            //OnceFeignProvider.voidExec(() -> betaFeignClient3.randomAddRole(), "http://127.0.0.1:8081");
            betaFeignClient4.randomAddRole();
            
            sysUser = new SysUserPO();
            sysUser.setId(IdWorker.getId());
            sysUser.setName("用户B");
            save(sysUser);
            System.err.println(1 / 0);// todo 在这里打断点，进入断点后观察两张表的数据；然后放行断点跑完请求，再观察两张表的数据； 对比可知：分布式事务生效了
            return null;
        });
    }
    
    
    /**
     * 手动事务测试。效果等价于 {@link SysUserService#testNested()}
     */
    @Override
    public SysUserPO testNestedManual() {
        return SeataManualTransactionSupport.create().exec(() -> {
            SysUserPO sysUser = new SysUserPO();
            sysUser.setId(IdWorker.getId());
            sysUser.setName("用户A");
            save(sysUser);
            
            // 示例几种常用的 restful调用远程服务的方式, 这几种方式seata都是支持的； 当然seata还支持更多的restful调用远程服务的方式，详见seata官网
            //betaFeignClient1.randomAddRole();
            //betaFeignClient2.randomAddRole();
            //OnceFeignProvider.voidExec(() -> betaFeignClient3.randomAddRole(), "http://127.0.0.1:8081");
            betaFeignClient4.randomAddRole();
            
            //sysUserService.addRandomUserRole();
            sysUserService.addRandomUserRoleManual();
            sysUser = new SysUserPO();
            sysUser.setId(IdWorker.getId());
            sysUser.setName("用户B");
            save(sysUser);
            System.err.println(1 / 0);// todo 在这里打断点，进入断点后观察两张表的数据；然后放行断点跑完请求，再观察两张表的数据； 对比可知：分布式事务生效了
            return null;
        });
    }
    
    @Override
    @Transactional(rollbackFor = Exception.class)
    @GlobalTransactional(rollbackFor = Exception.class)
    public SysUserPO addRandomUserRole() {
        SysUserPO sysUser = new SysUserPO();
        sysUser.setId(IdWorker.getId());
        sysUser.setName(RandomStringUtils.random(10, true, true));
        save(sysUser);
    
        // 示例几种常用的 restful调用远程服务的方式, 这几种方式seata都是支持的； 当然seata还支持更多的restful调用远程服务的方式，详见seata官网
        //betaFeignClient1.randomAddRole();
        //betaFeignClient2.randomAddRole();
        //OnceFeignProvider.voidExec(() -> betaFeignClient3.randomAddRole(), "http://127.0.0.1:8081");
        betaFeignClient4.randomAddRole();
        
        return sysUser;
    }
    
    /**
     * 手动事务测试。效果等价于 {@link SysUserService#addRandomUserRole()}
     */
    @Override
    public SysUserPO addRandomUserRoleManual() {
        return SeataManualTransactionSupport.create().exec(() -> {
            SysUserPO sysUser = new SysUserPO();
            sysUser.setId(IdWorker.getId());
            sysUser.setName(RandomStringUtils.random(10, true, true));
            save(sysUser);
    
            // 示例几种常用的 restful调用远程服务的方式, 这几种方式seata都是支持的； 当然seata还支持更多的restful调用远程服务的方式，详见seata官网
            //betaFeignClient1.randomAddRole();
            //betaFeignClient2.randomAddRole();
            //OnceFeignProvider.voidExec(() -> betaFeignClient3.randomAddRole(), "http://127.0.0.1:8081");
            betaFeignClient4.randomAddRole();
            
            return sysUser;
        });
    }
}
